import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import os
import matplotlib.pyplot as plt
import shutil
from typing import Optional, Dict, Union

# 显示设置
plt.rcParams['font.sans-serif'] = ['SimHei']  # 中文显示
plt.rcParams['axes.unicode_minus'] = False  # 负号显示
pd.options.mode.chained_assignment = None  # 关闭链式赋值警告

# 目录配置
DIR_CONFIG = {
    'base': 'insurance_data',
    'raw': 'raw_data',
    'metrics': {
        'national': 'metrics/national',
        'regions': ['East', 'West', 'North', 'South']
    },
    'reports': 'reports'
}


def create_dirs(clean=False):
    """创建标准化目录结构"""
    if clean and os.path.exists(DIR_CONFIG['base']):
        shutil.rmtree(DIR_CONFIG['base'])

    os.makedirs(f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}", exist_ok=True)
    os.makedirs(f"{DIR_CONFIG['base']}/{DIR_CONFIG['metrics']['national']}", exist_ok=True)
    for region in DIR_CONFIG['metrics']['regions']:
        os.makedirs(f"{DIR_CONFIG['base']}/metrics/{region}", exist_ok=True)
    os.makedirs(f"{DIR_CONFIG['base']}/{DIR_CONFIG['reports']}", exist_ok=True)


def generate_test_data():
    """生成测试数据集"""
    np.random.seed(42)
    regions = DIR_CONFIG['metrics']['regions']
    today = datetime.now().date()

    # 生成保单数据
    policies = []
    for i in range(1, 1001):
        is_canceled = np.random.choice([0, 1], p=[0.95, 0.05])
        start_date = today - timedelta(days=np.random.randint(1, 365 * 3))
        cancel_date = (start_date + timedelta(days=np.random.randint(1, 15))) if is_canceled else None

        policies.append({
            'policy_id': f'POL{i:05d}',
            'policy_type': np.random.choice(['life', 'health', 'annuity'], p=[0.6, 0.3, 0.1]),
            'is_group': np.random.choice([0, 1], p=[0.7, 0.3]),
            'is_chief_reinsurer': np.random.choice([0, 1], p=[0.4, 0.6]),
            'term_type': np.random.choice(['short', 'long'], p=[0.3, 0.7]),
            'payment_method': np.random.choice(['lump', 'installment'], p=[0.4, 0.6]),
            'installment_years': int(np.random.choice([5, 10, 15, 20], p=[0.3, 0.4, 0.2, 0.1])),
            'premium': round(np.random.uniform(1000, 50000), 2),
            'estimated_premium': round(np.random.uniform(1000, 50000) * 1.1, 2),
            'start_date': start_date.strftime('%Y-%m-%d'),
            'end_date': (today + timedelta(days=np.random.randint(365, 365 * 20))).strftime('%Y-%m-%d'),
            'is_canceled': is_canceled,
            'cancel_date': cancel_date.strftime('%Y-%m-%d') if cancel_date else None,
            'business_value': round(np.random.uniform(500, 50000), 2),
            'region': np.random.choice(regions)
        })

    # 生成资产数据
    assets = []
    for region in regions:
        base = np.random.uniform(1e8, 1e9)
        for i in range(36):  # 3年数据
            assets.append({
                'record_date': (today - timedelta(days=30 * (36 - i))).strftime('%Y-%m-%d'),
                'total_assets': round(base * (1 + np.random.uniform(-0.05, 0.1)), 2),
                'region': region
            })

    # 生成区域数据
    regions_df = pd.DataFrame({
        'region_id': [f'R{i:02d}' for i in range(1, len(regions) + 1)],
        'region_name': regions,
        'manager': [f'Manager_{r}' for r in regions]
    })

    return pd.DataFrame(policies), pd.DataFrame(assets), regions_df


def save_raw_data(policies: pd.DataFrame, assets: pd.DataFrame, regions: pd.DataFrame):
    """保存原始数据到raw目录"""
    policies.to_csv(f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}/policies.csv", index=False)
    assets.to_csv(f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}/assets.csv", index=False)
    regions.to_csv(f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}/regions.csv", index=False)


class InsuranceAnalyzer:
    def __init__(self):
        """加载数据并初始化"""
        self.policies = pd.read_csv(
            f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}/policies.csv",
            parse_dates=['start_date', 'end_date', 'cancel_date']
        )
        self.assets = pd.read_csv(
            f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}/assets.csv",
            parse_dates=['record_date']
        )
        self.regions = pd.read_csv(f"{DIR_CONFIG['base']}/{DIR_CONFIG['raw']}/regions.csv")

        # 数据校验
        self._validate_data()

    def _validate_data(self):
        """数据完整性检查"""
        required = {
            'policies': ['policy_id', 'start_date', 'premium', 'region'],
            'assets': ['record_date', 'total_assets', 'region']
        }
        for df_name, cols in required.items():
            df = getattr(self, df_name)
            missing = set(cols) - set(df.columns)
            if missing:
                raise ValueError(f"{df_name}缺少必要列: {missing}")

    def calculate_metrics(self, region: Optional[str] = None) -> pd.DataFrame:
        """计算核心业务指标 (修正版)"""
        metrics = {
            '首席再保人合同占比(%)': self._chief_ratio(region),
            '长期险保费增长率(%)': self._premium_growth(region),
            '短期险保费占比(%)': self._term_ratio(region),
            '犹豫期退保率(%)': self._cancellation_rate(region),
            '资产增量保费比(%)': self._asset_ratio(region)  # 现在返回单个数值
        }
        return pd.DataFrame.from_dict(metrics, orient='index', columns=['值'])

    def _chief_ratio(self, region: Optional[str]) -> float:
        """首席再保人占比"""
        df = self._filter_region(self.policies, region)
        return round(df['is_chief_reinsurer'].mean() * 100, 2)

    def _premium_growth(self, region: Optional[str] = None, return_all: bool = False) -> Union[float, pd.DataFrame]:
        """长期险保费增长率 (修正版)

        Args:
            region: 可选区域筛选
            return_all: 是否返回完整增长数据

        Returns:
            当return_all=False时返回最新增长率(float)
            当return_all=True时返回完整增长数据(DataFrame)
        """
        df = self._filter_region(self.policies, region)
        df = df.query("term_type == 'long'").copy()

        # 按月分组计算保费
        monthly = df.assign(period=df['start_date'].dt.to_period('M')) \
            .groupby('period')['premium'].sum() \
            .reset_index()

        # 计算增长率
        monthly['growth'] = monthly['premium'].pct_change().fillna(0)
        monthly['growth_pct'] = (monthly['growth'] * 100).round(2)

        if return_all:
            return monthly[['period', 'premium', 'growth_pct']].rename(columns={'growth_pct': 'growth'})
        return monthly.iloc[-1]['growth_pct'] if not monthly.empty else 0.0

    def _term_ratio(self, region: Optional[str]) -> float:
        """短期险占比"""
        df = self._filter_region(self.policies, region)
        term_totals = df.groupby('term_type')['premium'].sum()
        return round(term_totals.get('short', 0) / term_totals.sum() * 100, 2)

    def _cancellation_rate(self, region: Optional[str]) -> float:
        """犹豫期退保率"""
        df = self._filter_region(self.policies, region)
        df = df.query("policy_type in ['life', 'health']").copy()

        canceled = df.query("is_canceled == 1")
        in_grace = canceled[canceled['cancel_date'] - canceled['start_date'] <= timedelta(days=15)]

        total = df['premium'].sum()
        grace_premium = in_grace['premium'].sum()
        return round(grace_premium / total * 100, 2) if total > 0 else 0.0

    def _asset_ratio(self, region: Optional[str]) -> float:
        """资产增量保费比 (修正版)"""
        assets = self._filter_region(self.assets, region)
        policies = self._filter_region(self.policies, region)

        if len(assets) < 2:  # 至少需要两个时间点数据
            return 0.0

        # 获取最新和上一个记录
        latest_date = assets['record_date'].max()
        prev_date = assets[assets['record_date'] < latest_date]['record_date'].max()

        # 计算资产增量
        latest_assets = assets.set_index('record_date').loc[latest_date, 'total_assets']
        prev_assets = assets.set_index('record_date').loc[prev_date, 'total_assets']
        asset_diff = latest_assets - prev_assets

        # 计算近一年保费
        one_year_premium = policies[
            policies['start_date'] >= (latest_date - timedelta(days=365))
            ]['premium'].sum()

        return round(asset_diff / one_year_premium * 100, 2) if one_year_premium > 0 else 0.0

    def _filter_region(self, df: pd.DataFrame, region: Optional[str]) -> pd.DataFrame:
        """区域数据过滤"""
        if region:
            return df.query(f"region == '{region}'").copy()
        return df.copy()


def generate_visualization(metrics: pd.DataFrame, analyzer: InsuranceAnalyzer, region: Optional[str] = None):
    """生成可视化图表 (最终修正版)"""
    try:
        fig, axes = plt.subplots(2, 2, figsize=(15, 12))
        fig.suptitle(f'{region if region else "全国"}寿险业务指标', fontsize=16)

        # 1. 首席占比饼图
        chief = metrics.loc['首席再保人合同占比(%)', '值']
        axes[0, 0].pie([chief, 100 - chief],
                       labels=['首席', '非首席'],
                       autopct='%1.1f%%',
                       colors=['#ff9999', '#66b3ff'])
        axes[0, 0].set_title('首席再保人占比', pad=20)

        # 2. 长短期险占比
        short = metrics.loc['短期险保费占比(%)', '值']
        axes[0, 1].pie([short, 100 - short],
                       labels=['短期险', '长期险'],
                       autopct='%1.1f%%',
                       colors=['#99ff99', '#ffcc99'])
        axes[0, 1].set_title('产品结构占比', pad=20)

        # 3. 其他指标柱状图
        other = metrics.drop(['首席再保人合同占比(%)', '短期险保费占比(%)'])
        if not other.empty:
            colors = plt.cm.tab10(range(len(other)))
            bars = axes[1, 0].bar(other.index, other['值'], color=colors)
            axes[1, 0].set_title('其他业务指标', pad=20)
            axes[1, 0].tick_params(axis='x', rotation=45)
            axes[1, 0].bar_label(bars, fmt='%.2f%%', padding=3)

        # 4. 保费增长趋势
        growth_data = analyzer._premium_growth(region, return_all=True)
        if not growth_data.empty:
            axes[1, 1].plot(growth_data['period'].astype(str),
                            growth_data['growth'],
                            marker='o',
                            color='#ff7f0e')
            axes[1, 1].set_title('长期险保费增长趋势', pad=20)
            axes[1, 1].set_ylabel('增长率(%)')
            axes[1, 1].tick_params(axis='x', rotation=45)
            axes[1, 1].grid(True, linestyle='--', alpha=0.6)

        plt.tight_layout(pad=3.0)

        # 保存图片
        region_path = region.replace(' ', '_').lower() if region else 'national'
        os.makedirs(f"{DIR_CONFIG['base']}/metrics/{region_path}", exist_ok=True)
        plt.savefig(f"{DIR_CONFIG['base']}/metrics/{region_path}/metrics.png", dpi=300, bbox_inches='tight')
        plt.close()
    except Exception as e:
        print(f"生成可视化图表时出错: {str(e)}")
        if 'fig' in locals():
            plt.close(fig)


def export_results(metrics: pd.DataFrame, analyzer: InsuranceAnalyzer, region: Optional[str] = None):
    """导出结果到Excel (修正版)"""
    try:
        region_path = region.replace(' ', '_').lower() if region else 'national'
        os.makedirs(f"{DIR_CONFIG['base']}/metrics/{region_path}", exist_ok=True)
        path = f"{DIR_CONFIG['base']}/metrics/{region_path}/report.xlsx"

        with pd.ExcelWriter(path, engine='openpyxl') as writer:
            # 1. 核心指标表
            metrics.to_excel(writer, sheet_name='核心指标')

            # 2. 保费增长明细
            growth_data = analyzer._premium_growth(region, return_all=True)
            growth_data.to_excel(writer, sheet_name='保费增长', index=False)

            # 3. 产品结构明细
            term_data = analyzer.policies.groupby('term_type')['premium'].sum().reset_index()
            term_data['占比(%)'] = (term_data['premium'] / term_data['premium'].sum() * 100).round(2)
            term_data.to_excel(writer, sheet_name='产品结构', index=False)

            # 4. 区域资产情况
            if region:
                asset_data = analyzer.assets[analyzer.assets['region'] == region]
            else:
                asset_data = analyzer.assets
            asset_data.to_excel(writer, sheet_name='资产情况', index=False)

        print(f"成功生成报告: {path}")
    except Exception as e:
        print(f"导出报告时出错: {str(e)}")


def generate_summary_report():
    """生成全国汇总报告"""
    pass  # 实现逻辑与单区域类似


if __name__ == "__main__":
    # 初始化目录
    create_dirs(clean=True)

    # 生成并保存数据
    print("正在生成测试数据...")
    policies, assets, regions = generate_test_data()
    save_raw_data(policies, assets, regions)

    # 分析数据
    print("正在分析业务指标...")
    analyzer = InsuranceAnalyzer()

    # 全国报告
    print("\n全国业务指标:")
    national_metrics = analyzer.calculate_metrics()
    print(national_metrics)
    generate_visualization(national_metrics, analyzer)
    export_results(national_metrics, analyzer)

    # 区域报告
    for region in analyzer.regions['region_name']:
        print(f"\n{region}业务指标:")
        region_metrics = analyzer.calculate_metrics(region)
        print(region_metrics)
        generate_visualization(region_metrics, analyzer, region)
        export_results(region_metrics, analyzer, region)

    print("\n分析完成！结果已保存到以下目录:")
    print(f" - 原始数据: {DIR_CONFIG['base']}/{DIR_CONFIG['raw']}")
    print(f" - 分析报告: {DIR_CONFIG['base']}/metrics")